package symboltable;

import java.util.Hashtable;
import java.util.Vector;

import org.omg.CORBA.Current;

import symboltable.Class;
import syntaxtree.Type;
import syntaxtree.Formal;




public class SymbolTable {
	// Contains a table of classes, which themselves have their methods/variables(attributes)
	// Each method in turn has its own variables and parameters
	private Hashtable<Symbol, Class> class_table;
	
	private Class current_class;
	private Method current_method;
	
	
	public SymbolTable()
	{
		class_table = new Hashtable<Symbol, Class>();
		current_class = null;
		current_method = null;
	}
	
	// Returns the type of a symbol from the current scope
	public Type lookup(Symbol s)
	{
		if (current_method == null) {
			if (current_class != null) {
				Class current = current_class;
				while (current != null) {
					if (current.contains_variable(s)) {
						return current.get_variable_type(s);
					}
					Symbol extended = current.get_extended_class();
					if (extended != null) {
						current = class_table.get(extended);
					} else {
						current = null;
					}
				}
				return null;
			} else {
				return null;
			}
		} else {
			if (current_method.contains_variable(s)) {
				return current_method.get_variable_type(s);
			} else {
				Class current = current_class;
				while (current != null) {
					if (current.contains_variable(s)) {
						return current.get_variable_type(s);
					}
					Symbol extended = current.get_extended_class();
					if (extended != null) {
						current = class_table.get(extended);
					} else {
						current = null;
					}
				}
				return null;
			}
		}
	}
	
	public boolean contains_class(Symbol s)
	{
		return class_table.containsKey(s);
	}
	
	public Class get_current_class()
	{
		return current_class;
	}
	
	public Method get_current_method()
	{
		return current_method;
	}
	
	// Adds a new class starting its scope
	public Class add_class(Symbol cs, Symbol ex) 
	{
		Class new_class = null;
		if (cs.equals(ex)) {
			System.out.println("Class " + cs.toString() + " extending itself.");
			return null;
		}
		if (current_class != null && current_method != null)
		{
			new_class = null;
			System.out.println("Trying to start a class scope within another class or method.");
		} else {
			if (class_table.containsKey(cs)) {
				System.out.println("Class " + cs.toString() + " already declared.");
				new_class = null;
			} else {
				class_table.put(cs, new Class(cs, ex));
				new_class = class_table.get(cs);
			}
		} 
		return new_class;
	}
	
	// Adds a new class and starts its scope - without parents
	public void begin_class_scope(Symbol cs)
	{
		begin_class_scope(cs, null);
	}
	
	public void begin_class_scope(Symbol cs, Symbol ex)
	{
		if (current_class == null || current_method != null) {
			current_class = class_table.get(cs);
		} else {
			System.out.println("Class declaration within another class or method.");
		}
	}
	
	public void end_class_scope()
	{
		if (current_class != null && current_method == null) {
			current_class = null;
		} else {
			System.out.println("Class scope termination taking place within a method or without a class scope initialization");
		}
	}
	
	public Method add_method(Type ret, Symbol name, Vector<Formal> params)
	{
		if (current_method != null || current_class == null) {
			System.out.println("Trying to declare a method within a method or without an enclosing class");
			return null;
		}
		Vector<Method> meths = get_class_methods(current_class.get_class(), name); 
		boolean already_exists = false;
		for(Method m : meths) {
			if (m.compare_params_type(params)) {
				already_exists = true;
				break;
			}
		}
		if (!already_exists) {
			return current_class.add_method(name, params, ret);
		} else {
			System.out.println("Method " + name.toString() + " already defined.");
			return null;
		}
	}
	
	public void begin_method_scope(Type ret, Symbol name, Vector<Formal> params)
	{
		if (current_class != null && current_method == null) {
			for( Method m : current_class.get_methods(name) ) {
				if ( m.compare_params_type(params) ) {
					current_method = m;
				}
			}
			if (current_method == null) {
				System.out.println("There is no " + name.toString() + " method to start the scope.");
			}
		} else {
			System.out.println("Method scope beginning not taking place in a proper scope");
		}
	}
	
	public void end_method_scope()
	{
		if (current_class != null && current_method != null) {
			current_method = null;
		} else {
			System.out.println("Method scope termination taking place without a proper class scope or a method scope initialization");
		}
	}

	
	public void add_variable(Symbol s, Type t)
	{
		if ( current_class != null ) {
			if (current_class.contains_variable(s)) {
				System.out.println("Symbol " + s.toString() + " already defined." );
			} else {
				current_class.add_variable(s, t);
			}
		} else if ( current_method != null ) {
			if (current_method.contains_variable(s)) {
				System.out.println("Symbol " + s.toString() + " already defined." );
			} else {
				current_method.add_variable(s, t);
			}
		} else {
			// Should never reach this point
			System.out.println("No Scope to define the symbol " + s.toString());
		}
	}
	
	// Returns a vector containing all the methods for a given class, including its base classes methods
	public Vector<Method> get_class_methods(Symbol cs, Symbol me)
	{
		Vector<Method> methods = new Vector<Method>();
		
		Class current = class_table.get(cs);
		while (current != null) {
			methods.addAll(current.get_methods(me));
			Symbol extended = current.get_extended_class();
			if (extended != null) {
				current = class_table.get(extended);
			} else {
				current = null;
			}
		}
		
		return methods;
	}
}
